package com.marketfarm.server.service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.jdo.PersistenceManager;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import com.marketfarm.client.service.CompraVendaService;
import com.marketfarm.client.service.UsuarioService;
import com.marketfarm.server.dao.DAO;
import com.marketfarm.server.dao.PMF;
import com.marketfarm.shared.entity.Item;
import com.marketfarm.shared.entity.Item.TipoItem;
import com.marketfarm.shared.entity.Ordem.TipoOrdem;
import com.marketfarm.shared.entity.Ordem;
import com.marketfarm.shared.entity.Usuario;

public class CompraVendaServiceImpl extends RemoteServiceServlet implements CompraVendaService{

	private DAO dao = new DAO();
	private Map<TipoItem, Item> itens;
	private Map<TipoItem, List<Ordem>> ordensVenda;
	private Map<TipoItem, List<Ordem>> ordensAgregadasVenda;
	private Map<TipoItem, List<Ordem>> ordensCompra;
	private Map<TipoItem, List<Ordem>> ordensAgregadasCompra;
	private UsuarioServiceImpl usuarioService = new UsuarioServiceImpl();
	
	@Override
	public Ordem comprar(String sessao, TipoItem tipoItem, Integer quantidade, Double preco) throws Exception{
		Usuario usuario = usuarioService.getUsuario(sessao, getThreadLocalRequest().getSession());
		Double valorAtual = usuario.getDinheiro() - quantidade * preco;
		List<Ordem> listOrdens =
			dao.buscar(Ordem.class, new String[]{"usuarioKey", "tipoOrdem"}, new Object[]{usuario.getKey(), TipoOrdem.COMPRA});
		
		if(valorAtual >= 0){
			for(Ordem ordem : listOrdens){
				valorAtual -= ordem.getPreco() * ordem.getQuantidade();
			}
		}
		if(valorAtual < 0) throw new Exception("Você não possui dinheiro disponível para executar essa operação.");
		
		Integer quantidadeSobrando = quantidade;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ordem retorno = null;
		
		try {
			List<Ordem> listOrdem = ordensVenda.get(tipoItem);
			
			if(listOrdem != null){
				for(Ordem ordem : listOrdem){
					if(ordem.getPreco() <= preco){
						quantidade = Math.min(quantidadeSobrando, ordem.getQuantidade());
						quantidadeSobrando -= quantidade;
						executarCompra(usuario, quantidade, ordem, pm);
						ordensVenda = null;
						
						if(quantidadeSobrando == 0){
							break;
						}
					}
					else break;
				}
			}
			
			if(quantidadeSobrando > 0){
				retorno = new Ordem();
				retorno.setPreco(preco);
				retorno.setQuantidade(quantidadeSobrando);
				retorno.setTipoItem(tipoItem);
				retorno.setTipoOrdem(TipoOrdem.COMPRA);
				retorno.setUsuarioKey(usuario.getKey());
				
				dao.merge(retorno, pm);
				ordensCompra = null;
			}
		} finally {
			pm.close();
		}
		
		getOrdensAtuais(TipoItem.MACA, 0L);
		
		return retorno;
	}

	private void executarCompra(Usuario usuario, Integer quantidade, Ordem venda, PersistenceManager pm) throws Exception{
		Usuario usuarioVenda = dao.load(pm, Usuario.class, venda.getUsuarioKey());

		Map<TipoItem, Integer> usuVendaItens = usuarioVenda.getQuantidadeItem();
		usuVendaItens.put(venda.getTipoItem(), usuVendaItens.get(venda.getTipoItem()) - quantidade);
		Map<TipoItem, Integer> usuVendaItensAVenda = usuarioVenda.getQuantidadeItemAVenda();
		usuVendaItensAVenda.put(venda.getTipoItem(), usuVendaItensAVenda.get(venda.getTipoItem()) - quantidade);
		usuarioVenda.setDinheiro(usuarioVenda.getDinheiro() + quantidade * venda.getPreco());
		
		Map<TipoItem, Integer> usuCompraItens = usuario.getQuantidadeItem();
		usuCompraItens.put(venda.getTipoItem(), usuCompraItens.get(venda.getTipoItem()) + quantidade);
		usuario.setDinheiro(usuario.getDinheiro() - quantidade * venda.getPreco());
		
		if(quantidade >= venda.getQuantidade()){
			dao.delete(venda, pm);
		}
		else{
			venda.setQuantidade(venda.getQuantidade() - quantidade);
			dao.merge(venda, pm);
		}
		
		dao.merge(usuario, pm);
		dao.merge(usuarioVenda, pm);
		
		Item item = itens.get(venda.getTipoItem());
		item.setValor(venda.getPreco());
		dao.merge(item);
	}

	@Override
	public Ordem vender(String sessao, TipoItem tipoItem, Integer quantidade, Double preco) throws Exception{
		Usuario usuario = usuarioService.getUsuario(sessao, getThreadLocalRequest().getSession());
		Map<TipoItem, Integer> itensUsuario = usuario.getQuantidadeItem();
		List<Ordem> listOrdens =
			dao.buscar(Ordem.class, new String[]{"usuarioKey", "tipoItem", "tipoOrdem"}, new Object[]{usuario.getKey(), tipoItem, TipoOrdem.VENDA});
		
		boolean valid = false;
		if(itensUsuario != null){
			for(Entry<TipoItem, Integer> entry : itensUsuario.entrySet()){
				if(entry.getKey().equals(tipoItem) && entry.getValue() >= quantidade){
					if(listOrdens != null){
						Integer qtdAVenda = 0;
						for (Ordem ordem : listOrdens) {
							qtdAVenda += ordem.getQuantidade();
						}
						if(qtdAVenda + quantidade > entry.getValue()) break; //Inválido
					}
					valid = true;
					break;
				}
			}
		}
		if(!valid) throw new Exception("Você não possui ações disponíveis para executar essa operação.");
		
		Integer quantidadeSobrando = quantidade;
		PersistenceManager pm = PMF.get().getPersistenceManager();
		Ordem retorno = null;
		
		try {
			List<Ordem> listOrdem = ordensCompra.get(tipoItem);
			
			if(listOrdem != null){
				for(Ordem ordem : listOrdem){
					if(ordem.getPreco() >= preco){
						quantidade = Math.min(quantidadeSobrando, ordem.getQuantidade());
						quantidadeSobrando -= quantidade;
						executarVenda(usuario, quantidade, ordem, pm);
						ordensCompra = null;
						
						if(quantidadeSobrando == 0){
							break;
						}
					}
					else break;
				}
			}
			
			if(quantidadeSobrando > 0){
				retorno = new Ordem();
				retorno.setPreco(preco);
				retorno.setQuantidade(quantidadeSobrando);
				retorno.setTipoItem(tipoItem);
				retorno.setTipoOrdem(TipoOrdem.VENDA);
				retorno.setUsuarioKey(usuario.getKey());
				
				dao.merge(retorno, pm);
				
				Map<TipoItem, Integer> itensAVenda = usuario.getQuantidadeItemAVenda();
				itensAVenda.put(tipoItem, itensAVenda.get(tipoItem) + quantidadeSobrando);
				
				dao.merge(usuario);
				
				ordensVenda = null;
			}
		} finally {
			pm.close();
		}
		
		getOrdensAtuais(TipoItem.MACA, 0L);
		
		return retorno;
	}
	
	private void executarVenda(Usuario usuario, Integer quantidade, Ordem compra, PersistenceManager pm) throws Exception{
		Usuario usuarioCompra = dao.load(pm, Usuario.class, compra.getUsuarioKey());

		Map<TipoItem, Integer> usuCompItens = usuarioCompra.getQuantidadeItem();
		usuCompItens.put(compra.getTipoItem(), usuCompItens.get(compra.getTipoItem()) + quantidade);
		usuarioCompra.setDinheiro(usuarioCompra.getDinheiro() - quantidade * compra.getPreco());
		
		Map<TipoItem, Integer> usuVendaItens = usuario.getQuantidadeItem();
		usuVendaItens.put(compra.getTipoItem(), usuVendaItens.get(compra.getTipoItem()) - quantidade);
		Map<TipoItem, Integer> usuVendaItensAVenda = usuario.getQuantidadeItemAVenda();
		usuVendaItensAVenda.put(compra.getTipoItem(), usuVendaItensAVenda.get(compra.getTipoItem()) + quantidade);
		usuario.setDinheiro(usuario.getDinheiro() + quantidade * compra.getPreco());
		
		if(quantidade >= compra.getQuantidade()){
			dao.delete(compra, pm);
		}
		else{
			compra.setQuantidade(compra.getQuantidade() - quantidade);
			dao.merge(compra, pm);
		}
		
		dao.merge(usuario, pm);
		dao.merge(usuarioCompra, pm);
	}

	@Override
	public Map<TipoOrdem, List<Ordem>> getOrdensAtuais(TipoItem tipoItem, Long avoidCache) throws Exception{
		if(ordensVenda == null){
			ordensVenda = new HashMap<TipoItem, List<Ordem>>();
			ordensAgregadasVenda = new HashMap<TipoItem, List<Ordem>>();
			
			List<Ordem> retorno = dao.buscar(Ordem.class, new String[]{"tipoOrdem"}, new Object[]{TipoOrdem.VENDA}, "preco asc");
			for (Ordem ordem : retorno) {
				List<Ordem> listaVenda = ordensVenda.get(ordem.getTipoItem());
				List<Ordem> listaAgregadaVenda = ordensAgregadasVenda.get(ordem.getTipoItem());
				
				if(listaVenda == null){
					listaVenda = new ArrayList<Ordem>();
					listaAgregadaVenda = new ArrayList<Ordem>();
					ordensVenda.put(ordem.getTipoItem(), listaVenda);
					ordensAgregadasVenda.put(ordem.getTipoItem(), listaAgregadaVenda);
				}
				listaVenda.add(ordem);
				
				int size = listaAgregadaVenda.size(); 
				if(size > 0){
					Ordem lastOrdem = listaAgregadaVenda.get(size-1);
					if(lastOrdem.getPreco().equals(ordem.getPreco())){
						lastOrdem.setQuantidade(lastOrdem.getQuantidade() + ordem.getQuantidade());
					}
					else listaAgregadaVenda.add(ordem);
				}
				else listaAgregadaVenda.add(ordem);
			}
			
			//Deixa apenas 10 itens em cada lista
			for(Entry<TipoItem, List<Ordem>> ordem : ordensAgregadasVenda.entrySet()){
				if(ordem.getValue().size() > 10) ordensAgregadasVenda.put(ordem.getKey(), ordem.getValue().subList(0, 10));
			}
		}
		
		if(ordensCompra == null){
			ordensCompra = new HashMap<TipoItem, List<Ordem>>();
			ordensAgregadasCompra = new HashMap<TipoItem, List<Ordem>>();
			
			List<Ordem> retorno = dao.buscar(Ordem.class, new String[]{"tipoOrdem"}, new Object[]{TipoOrdem.COMPRA}, "preco desc");
			for (Ordem ordem : retorno) {
				List<Ordem> listaCompra = ordensCompra.get(ordem.getTipoItem());
				List<Ordem> listaAgregadaCompra = ordensAgregadasCompra.get(ordem.getTipoItem());
				
				if(listaCompra == null){
					listaCompra = new ArrayList<Ordem>();
					listaAgregadaCompra = new ArrayList<Ordem>();
					ordensCompra.put(ordem.getTipoItem(), listaCompra);
					ordensAgregadasCompra.put(ordem.getTipoItem(), listaAgregadaCompra);
				}
				listaCompra.add(ordem);
				
				int size = listaAgregadaCompra.size(); 
				if(size > 0){
					Ordem lastOrdem = listaAgregadaCompra.get(size-1);
					if(lastOrdem.getPreco().equals(ordem.getPreco())){
						lastOrdem.setQuantidade(lastOrdem.getQuantidade() + ordem.getQuantidade());
					}
					else listaAgregadaCompra.add(ordem);
				}
				else listaAgregadaCompra.add(ordem);
			}
			
			//Deixa apenas 10 itens em cada lista
			for(Entry<TipoItem, List<Ordem>> ordem : ordensAgregadasCompra.entrySet()){
				if(ordem.getValue().size() > 10) ordensAgregadasCompra.put(ordem.getKey(), ordem.getValue().subList(0, 10));
			}
		}
		
		Map<TipoOrdem, List<Ordem>> retorno = new HashMap<TipoOrdem, List<Ordem>>();
		retorno.put(TipoOrdem.COMPRA, ordensAgregadasCompra.get(tipoItem));
		retorno.put(TipoOrdem.VENDA, ordensAgregadasVenda.get(tipoItem));
		
		return retorno;
	}

	@Override
	public Map<TipoItem, Item> getItens(Long avoidCache) throws Exception{
		if(itens == null){
			List<Item> retorno = dao.buscar(Item.class, null, null);
			itens = new HashMap<TipoItem, Item>();
			for (Item item : retorno) {
				itens.put(item.getTipo(), item);
			}
		}
		
		return itens;
	}

	@Override
	public boolean removerOrdem(Ordem ordem) {
		// TODO Auto-generated method stub
		return false;
	}

}
